/*
* Copyright 2010 Pavel Kozhin 
*
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed  under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License.
*
* Author: Pavel Kozhin.
*/

package org.bookmarksframework.navigation
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	
	import mx.rpc.IResponder;
	import mx.rpc.Responder;
	
	import org.bookmarksframework.core.IDestroyable;
	import org.bookmarksframework.events.BookmarksEvent;
	import org.bookmarksframework.events.BookmarksPrecommitEvent;
	import org.bookmarksframework.manager.IBookmarksManager;
	import org.bookmarksframework.navigation.events.NavigationConditionChangeEvent;
	import org.bookmarksframework.navigation.events.NavigationConditionValidateEvent;
	import org.bookmarksframework.navigation.events.NavigationStateChangeEvent;
	import org.bookmarksframework.navigation.events.NavigationStateChangingEvent;
	import org.bookmarksframework.navigation.state.IStateController;
	import org.bookmarksframework.navigation.state.NavigationState;
	import org.bookmarksframework.navigation.state.StateController;
	
	[Event(name="stateChanging", type="org.bookmarksframework.navigation.events.NavigationStateChangingEvent")]
	
	[Event(name="stateChange", type="org.bookmarksframework.navigation.events.NavigationStateChangeEvent")]
	
	[Event(name="conditionChange", type="org.bookmarksframework.navigation.events.NavigationConditionChangeEvent")]
	
	[Event(name="conditionValidate", type="org.bookmarksframework.navigation.events.NavigationConditionValidateEvent")]
	
	public class Navigation extends EventDispatcher implements INavigation
	{
		private static var impl:INavigation;
		
		private static var lockInstantiation:Boolean = true;
		
		protected var stateController:IStateController;
		
		public function Navigation()
		{
			if (lockInstantiation)
				throw new Error("For instantiation use Navigation.getInstance() method.");
		}
		
		public static function getInstance():INavigation
		{
			if (!impl)
			{
				lockInstantiation = false;
				impl = new Navigation();
				lockInstantiation = true;
			}
			
			return impl;
		}
		
		private var _manager:IBookmarksManager = null;
		
		[Bindable("managerChanged")]
		public function get manager():IBookmarksManager
		{
			return _manager;
		}
		
		private var _currentState:NavigationState = null; 
		
		[Bindable("stateChange")]
		public function get currentState():NavigationState
		{
			return _currentState;
		}
		
		protected var _history:INavigationHistory;
		
		[Bindable("historyChange")]
		public function get history():INavigationHistory
		{
			return _history;
		}
		
		public function get isStateChanging():Boolean
		{
			return manager ? manager.isPrecommitting : false;
		}
		
		public function initialize(manager:IBookmarksManager):void
		{
			if (!manager)
				return;
			
			if (_manager)
			{
				_manager.removeEventListener(BookmarksEvent.UPDATE_COMPLETE, updateCompleteHandler);
				_manager.removeEventListener(BookmarksPrecommitEvent.PRECOMMIT, precommitHandler);
				_manager.removeEventListener(BookmarksEvent.READY, readyHandler);
			}
			
			_manager = manager;
			manager.addEventListener(BookmarksEvent.UPDATE_COMPLETE, updateCompleteHandler);
			manager.addEventListener(BookmarksPrecommitEvent.PRECOMMIT, precommitHandler);
			manager.addEventListener(BookmarksEvent.READY, readyHandler, false, Number.MAX_VALUE);
			
			if (stateController)
			{
				stateController.removeEventListener(
					NavigationConditionChangeEvent.CONDITION_CHANGE, redispatch);
				stateController.removeEventListener(
					NavigationConditionValidateEvent.VALIDATE, redispatch);
				stateController.destroy();
			}
				
			stateController = new StateController();
			stateController.addEventListener(
				NavigationConditionChangeEvent.CONDITION_CHANGE, redispatch);
				stateController.addEventListener(
					NavigationConditionValidateEvent.VALIDATE, redispatch);
			
			if (_history is IDestroyable)
				IDestroyable(_history).destroy();
			
			_history = new NavigationHistory(manager.config.browserAdapter, 
				manager.config.localHistory);
			
			dispatchEvent(new Event("managerChanged"));
			dispatchEvent(new Event("historyChange"));
			dispatchEvent(new Event("stateChange"));
		}
		
		public function importStates(map:XML, lazy:Boolean = true):void
		{
			if (!map)
				return;
			
			try {
				stateController.initialize(map, lazy);
			} catch (e:Error) {
				trace(e.toString());
			}
		}
		
		public function setState(stateOrPath:String):void
		{
			if (stateOrPath && !isStateChanging)
			{
				try {
					var newState:NavigationState = stateController.getState(stateOrPath);
					var responder:IResponder;
					
					update(newState, true);
				} catch (e:Error) {
					trace(e.toString());
				}
			}
		}
		
		public function getState(stateOrPath:String):NavigationState
		{
			try {
				return stateController.getState(stateOrPath);
			} catch (e:Error) {
				trace(e.toString());
			}
			
			return null;
		}
		
		protected function precommitHandler(event:BookmarksPrecommitEvent):void
		{
			try {
				var newState:NavigationState = stateController.getState(event.path.value);
				var responder:IResponder;
				
				if (!currentState)
				{
					event.responder.result(null);
					return;
				}
				else if (!newState.enabled)
				{
					trace("Navigation: State \""+newState.id+"\" is disabled.");
					event.responder.fault(null);
					return;
				}
				
				if (hasEventListener(NavigationStateChangingEvent.STATE_CHANGING))
				{
					responder = new Responder(function(value:Object):void {
												event.responder.result(null);
												changeState(newState);
											  }, function(value:Object):void {
											  	event.responder.fault(null);
											  });
					changingStatesHelper(currentState, newState, responder);
				}
			} catch (e:Error) {
				event.responder.fault(null);
			}
		}
		
		protected function updateCompleteHandler(event:BookmarksEvent):void
		{
			var path:String = event.path.value;
			
			try {
				if (!currentState)
				{
					update(stateController.getState(path), false)
				}
				else if (!currentState.match(path))
				{
					update(stateController.getState(path), false);
				}
			} catch (e:Error) {
				trace(e.toString());
			}
		}
		
		protected function readyHandler(event:BookmarksEvent):void
		{
			if (!currentState)
			{
				var state:NavigationState = getState(event.path.value as String);
				update(state, manager.activePath ? manager.activePath.value != event.path.value : true);
			}
		}
		
		protected function update(state:NavigationState, commit:Boolean):void
		{
			if (state)
			{
				if (state.paths.length && commit)
				{
					manager.commit(state.path);
				}
				else
				{
					changeState(state);
				}
			}
		}
		
		protected function changeState(newState:NavigationState):void
		{
			var oldState:NavigationState = _currentState;
			_currentState = newState;
			dispatchEvent(new NavigationStateChangeEvent(oldState, newState));
		}
		
		protected function redispatch(event:Event):void
		{
			dispatchEvent(event.clone());
		}
		
		protected function changingStatesHelper(oldState:NavigationState, newState:NavigationState, 
												responder:IResponder):void
		{
			var evt:NavigationStateChangingEvent = 
				new NavigationStateChangingEvent(oldState, newState, responder);
			
			dispatchEvent(evt);
		}
	}
}